package com.gentics.labs.nop;

import java.io.File;
import java.io.FileInputStream;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.IndexWriter;

import com.gentics.api.lib.datasource.Datasource;
import com.gentics.api.lib.expressionparser.ExpressionParser;
import com.gentics.api.lib.expressionparser.filtergenerator.DatasourceFilter;
import com.gentics.api.lib.resolving.Resolvable;
import com.gentics.api.portalnode.connector.PortalConnectorFactory;

public class CRIndexer {
	public final static String RULE_PARAMETER_NAME = "rule";
	public final static String HANDLE_PARAMETER_NAME = "handle";
	public final static String ATTR_PARAMETER_NAME = "attr";
	public final static String HELP_PARAMETER_NAME = "help";
	public final static String OUT_PARAMETER_NAME = "out";
	private static final String BATCHSIZE_PARAMETER_NAME = "batchsize";
	private static final int DEFAULT_BATCHSIZE = 100;
	private final static String DEFAULT_RULE = "1";

	private static Datasource ds;

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// init CommandLineParser and CommandLine
		CommandLineParser parser = new GnuParser();
		CommandLine line = null;

		try {
			line = parser.parse(createOptions(), args);
		} catch (ParseException e) {
			System.err.println("Invalid arguments found.");
			e.printStackTrace(System.err);
		}

		// empty line or help message
		if (line == null) {
			printHelpAndExit();
		}
		if (line.hasOption(HELP_PARAMETER_NAME)) {
			printHelpAndExit();
		}

		String allAttr = line.getOptionValue(ATTR_PARAMETER_NAME);
		allAttr += ",contentid";
		String[] attr = allAttr.split(",");
		for (int i = 0; i < attr.length; i++) {
			attr[i] = attr[i].trim();
		}
		String handleFile = line.getOptionValue(HANDLE_PARAMETER_NAME);
		String rule = line.getOptionValue(RULE_PARAMETER_NAME);

		if (rule == null) {
			rule = DEFAULT_RULE;
		}

		String outDirPath = line.getOptionValue(OUT_PARAMETER_NAME);
		File outDir = new File(outDirPath);

		int batchSize = DEFAULT_BATCHSIZE;
		String batchSizeValue = line.getOptionValue(BATCHSIZE_PARAMETER_NAME);
		if (batchSizeValue != null) {
			batchSize = Integer.parseInt(batchSizeValue);
		}

		try {
			Properties handleProps = new Properties();
			handleProps.load(new FileInputStream(new File(handleFile)));
			ds = PortalConnectorFactory.createDatasource(handleProps);

			// create an index writer
			IndexWriter writer = new IndexWriter(outDir,
					new StandardAnalyzer(), true,
					IndexWriter.MaxFieldLength.LIMITED);

			DatasourceFilter filter = ds
					.createDatasourceFilter(ExpressionParser.getInstance()
							.parse(rule));
			int countObjects = ds.getCount(filter);

			System.out
					.println("Start indexing of " + countObjects + " objects");
			int perc = 0;
			int count = 0;
			Collection objects = null;
			int startIndex = 0;
			Datasource.Sorting[] sorting = new Datasource.Sorting[] { new Datasource.Sorting(
					"contentid", Datasource.SORTORDER_ASC) };
			do {
				objects = ds.getResult(filter, attr, startIndex, batchSize, sorting);
				startIndex += batchSize;
				for (Iterator iterator = objects.iterator(); iterator.hasNext();) {
					Resolvable object = (Resolvable) iterator.next();
					writer.addDocument(getDocument(object, attr));
					count++;
					if (count * 10 / countObjects > perc) {
						perc = count * 10 / countObjects;
						System.out.println(perc * 10 + "% done");
					}
				}
			} while (objects.size() > 0);
			
			writer.optimize();
			writer.close();
			System.out.println("Indexing done.");
			System.exit(0);
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(-1);
		}
	}

	private final static Document getDocument(Resolvable resolvable,
			String[] attributes) {
		Document doc = new Document();
		for (int i = 0; i < attributes.length; i++) {
			Object value = resolvable.getProperty(attributes[i]);
			Store store = "contentid".equals(attributes[i]) ? Field.Store.YES
					: Field.Store.NO;
			if (value instanceof String) {
				doc.add(new Field(attributes[i], (String) value, store,
						Field.Index.ANALYZED));
			} else if (value instanceof byte[]) {
				// doc
				// .add(new Field(attributes[i], (byte[]) value,
				// Field.Store.NO));
			} else if (value instanceof Number) {
				doc.add(new Field(attributes[i], value.toString(), store,
						Field.Index.ANALYZED));
			}
		}
		return doc;
	}

	/**
	 * Private helper method to print the help screen and exit
	 */
	private static void printHelpAndExit() {
		HelpFormatter formatter = new HelpFormatter();

		formatter.printHelp("CRIndexer", createOptions());

		System.exit(0);
	}

	/**
	 * Private helper method to create all options
	 * 
	 * @return command line options
	 */
	private static Options createOptions() {
		Options options = new Options();
		options.addOption(HELP_PARAMETER_NAME, false, "print help");
		Option handleoption = new Option(
				HANDLE_PARAMETER_NAME,
				true,
				"handle.properties file with the connection properties to the content repository that will be indexed");
		handleoption.setRequired(true);
		options.addOption(handleoption);
		Option attroption = new Option(ATTR_PARAMETER_NAME, true,
				"comman separated list of attributes that will be made searchable");
		attroption.setRequired(true);
		options.addOption(attroption);
		options.addOption(RULE_PARAMETER_NAME, true,
				"Rule to specify the objects which will be indexed");
		options.addOption(new Option(BATCHSIZE_PARAMETER_NAME, true,
				"Number of objects that will be indexed in one step"));
		Option outoption = new Option(OUT_PARAMETER_NAME, true,
				"Path to the directory that will contain the index");
		outoption.setRequired(true);
		options.addOption(outoption);
		return options;
	}
}
